package com.yang.strategy.flter.impl;

import com.yang.common.constant.CacheConstant;
import com.yang.common.constant.SmsConstant;
import com.yang.common.enums.ExceptionEnums;
import com.yang.common.exception.StrategyException;
import com.yang.common.model.StandardSubmit;
import com.yang.strategy.client.BeaconCacheClient;
import com.yang.strategy.flter.StrategyFilter;
import com.yang.strategy.util.ErrorSendMsgUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import sun.misc.Cache;


import java.time.LocalDateTime;
import java.time.ZoneOffset;

/**
 * 60S内限制发1次
 *
 * @author yangyongsheng
 * @date 2024/9/8 23:15
 * @description
 */
@Service("limitOneMinute")
@Slf4j
public class LimitOneMinuteStrategyFilter implements StrategyFilter {

    //中国是东八区
    private final String UTC = "+8";

    private final long ONE_MINUTE = 60 * 1000 - 1;//一分钟内，所以减1

    @Autowired
    private BeaconCacheClient cacheClient;

    @Autowired
    private ErrorSendMsgUtil sendMsgUtil;

    @Override
    public void strategy(StandardSubmit submit) {

        //只有验证码类的短信才需要做限流操作
        if (!submit.getState().equals(SmsConstant.CODE_TYPE)){
            return;
        }
        log.info("【策略模块-1分钟限流校验】   校验ing…………");

        //1、基于submit获取短信的发送时间
        LocalDateTime sendTime = submit.getSendTime();

        //2、LocalDateTime -> Long 精确到毫秒值
        long sendTimeMilli = sendTime.toInstant(ZoneOffset.of(UTC)).toEpochMilli();

        //3、基于submit获取客户标识以及手机号信息
        Long clientId = submit.getClientId();
        String mobile = submit.getMobile();

        //4、优先将当前短信发送信息插入到Redis的ZSet结构中 zadd
        String key = CacheConstant.LIMIT_MINUTES + clientId + CacheConstant.SEPARATE + mobile;
        Boolean result = cacheClient.zadd(key, sendTimeMilli, sendTimeMilli);

        //5、如果查询失败，直接告辞，有并发情况，60s不能发送两条，直接告辞
        //有并发情况说明同一客户同一手机号，至少有两个进行发短信操作，满足60s限流规则
        if (!result) {
            log.info("【策略模块-一分钟限流策略】  插入失败！ 满足一分钟限流规则，无法发送！");
            submit.setErrorMsg(ExceptionEnums.ONE_MINUTE_LIMIT + ",mobile = " + mobile);
            sendMsgUtil.sendWriteLog(submit);
            sendMsgUtil.sendPushReport(submit);
            throw new StrategyException(ExceptionEnums.ONE_MINUTE_LIMIT);
        }

        //6、基于zrangebyscore查询1分钟之前，是否只有当前插入的发送短信信息
        long min = sendTimeMilli - ONE_MINUTE;
        Integer count = cacheClient.zrangebyscore(key, Double.parseDouble(min + ""), Double.parseDouble(sendTimeMilli + ""));

        //7、如果大于等于2条短信信息，60s前至少已经有一条短信插入，达到了60s一条的短信限流规则()，直接告辞。
        //大于等于两条说明,60s内有大于两天的短信内容存入到redis中了，满足60s限流规则
        if (count > 1) {
            // 一分钟之前，发送过短信，限流规则生效
            log.info("【策略模块-一分钟限流策略】  插入失败！ 满足一分钟限流规则，无法发送！");
            //把触发限流规则这个缓存给删除掉，不能放在if的外面，因为会把没有触发限流规则的缓存删除
            cacheClient.zremove(key, sendTimeMilli);
            submit.setErrorMsg(ExceptionEnums.ONE_MINUTE_LIMIT + ",mobile = " + mobile);
            sendMsgUtil.sendWriteLog(submit);
            sendMsgUtil.sendPushReport(submit);
            throw new StrategyException(ExceptionEnums.ONE_MINUTE_LIMIT);
        }

        log.info("【策略模块-一分钟限流策略】  一分钟限流规则通过，可以发送！");
    }
}
